package taskhandler

import (
	"cvevulner/common"
	"cvevulner/models"
	"cvevulner/util"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"
)

var wg sync.WaitGroup
var issueLock sync.Mutex

//OrgInfo
type OrgInfo struct {
	ID           int32  `json:"id,omitempty"`
	Login        string `json:"login,omitempty"`
	URL          string `json:"url,omitempty"`
	AvatarURL    string `json:"avatar_url,omitempty"`
	ReposURL     string `json:"repos_url,omitempty"`
	EventsURL    string `json:"events_url,omitempty"`
	MembersURL   string `json:"members_url,omitempty"`
	Description  string `json:"description,omitempty"`
	Name         string `json:"name,omitempty"`
	Enterprise   string `json:"enterprise,omitempty"`
	Members      int64  `json:"members,omitempty"`
	PublicRepos  int64  `json:"public_repos,omitempty"`
	PrivateRepos int64  `json:"private_repos,omitempty"`
}

//Branch Get all branches
type Branch struct {
	Name          string `json:"name,omitempty"`
	Protected     bool   `json:"protected,omitempty"`
	ProtectionURL string `json:"protection_url,omitempty"`
}

//PackageInfo package info model
type PackageInfo struct {
	Code string
	Msg  string
	Data Info
}

//Info cve info
type Info struct {
	Description string
}

//GrabIssueByOrg grab issue by org name
func GrabIssueByOrg(accToken, org string) error {
	logs.Info("Synchronize gitee's issue start......")
	orgInfo, err := GetOrgInfo(accToken, org)
	if err != nil {
		logs.Error("GetOrgInfo, org: ", org, ",err: ", err)
		return err
	}
	reposNum := orgInfo.PublicRepos + orgInfo.PrivateRepos
	if reposNum <= 0 {
		logs.Info(fmt.Sprintf("%v contain %v repository,grab issue finish!", org, reposNum))
		return errors.New(fmt.Sprintf("%v contain %v repository,grab issue finish!", org, reposNum))
	}
	pageSize := reposNum / int64(perPage)
	if reposNum%int64(perPage) > 0 {
		pageSize = pageSize + 1
	}
	var i int64
	for i = 1; i <= pageSize; i++ {
		go GetOrgRepos(accToken, org, i)
	}
	wg.Wait()
	logs.Info("Synchronize gitee's issue  finish...")
	return nil
}

//GrabIssueByRepo grab issue by repository
func GrabIssueByRepo(accToken, owner, repo, state string) {
	page := 1
	product, err := GetInfProduct(accToken, owner, repo)
	if err != nil {
		logs.Error("GetInfProduct, err: ", err)
	}
	desc := GetRepoDescription(repo)
	for {
		list, err := GetIssueList(accToken, owner, repo, state, page)
		if err != nil {
			logs.Error("GetIssueList, repo: ", repo, ",err: ", err)
			break
		}
		handleIssueList(list, product, desc)
		if len(list) < perPage {
			break
		}
		page++
	}
}

func GetInfProduct(token string, owner string, repo string) (infPro string, err error) {
	defer common.Catchs()
	resp, err := http.Get(fmt.Sprintf(GiteRepoBranch, owner, repo, token))
	if err != nil {
		logs.Error("url: ", GiteRepoBranch, ",repo:", repo, ",err: ", err)
		return "", err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		logs.Error("ReadAll: ", GiteRepoBranch, ",repo:", repo, ",err: ", err)
		return "", err
	}
	var branchList []Branch
	err = json.Unmarshal(body, &branchList)
	if err != nil {
		logs.Error("Unmarshal: ", GiteRepoBranch, ",repo:", repo, ",err: ", err)
		return "", err
	}
	affectBranchsxList := []string{}
	affectedBranchs := beego.AppConfig.String("cve::affected_branchs")
	if affectedBranchs != "" && len(affectedBranchs) > 0 {
		affectBranchsxList = strings.Split(affectedBranchs, ",")
	}
	tempBrandList := []string{}
	for _, v := range branchList {
		if affectBranchsxList != nil && len(affectBranchsxList) > 0 {
			for _, affectBrand := range affectBranchsxList {
				if strings.HasSuffix(v.Name, affectBrand) {
					tempBrandList = append(tempBrandList, v.Name)
					break
				}
			}
		}
	}
	if tempBrandList != nil && len(tempBrandList) > 0 {
		infPro = strings.Join(tempBrandList, "/")
	}
	return
}

func handleIssueList(list []models.GitIssue, product, desc string) {
	if len(list) == 0 {
		return
	}
	//var gil []models.GiteOriginIssue
	for _, v := range list {
		issueTitle := util.TrimString(v.Title)
		issueType := util.TrimString(v.IssueType)
		issueNumber := util.TrimString(v.Number)
		issueState := util.TrimString(v.State)
		issueZhState := util.TrimString(v.IssueState)
		repoPath := util.TrimString(v.Repository.Path)
		nameSpace := util.TrimString(v.Repository.NameSpace.Path)
		organizationID := int8(1)
		organizationID = GetOrganizationId(nameSpace)
		issueTitle = RegMatchCve(issueTitle)
		if issueType == CIssueType || strings.HasPrefix(issueTitle, "CVE") {
			item := models.GiteOriginIssue{IssueId: v.Id, Url: v.HtmlUrl,
				Number: issueNumber, State: issueState, Title: issueTitle,
				IssueType: issueType, SecurityHole: v.SecurityHole,
				IssueCreateAt: v.CreateAt, IssueUpdateAt: v.UpdateAt,
				IssueFinishAt: v.FinishedAt, IssueCreate: v.User.Login,
				IssueAssignee: v.Assignee.Login, RepoPath: repoPath,
				RepoUrl: v.Repository.Url, InfProduct: product,
				RepoDesc: desc, IssueState: issueZhState,
				Owner: nameSpace, OrganizationID: organizationID, Status: 0}
			//vt := util.TrimString(v.Title)
			if strings.HasPrefix(issueTitle, "CVE") {
				item.CveNumber = issueTitle
			} else if v.Body != "" {
				// Use regular expressions to intercept the body and improve it later
				sm := util.RegexpCveNumber.FindAllStringSubmatch(v.Body, -1)
				if len(sm) > 0 && len(sm[0]) > 0 {
					item.CveNumber = util.TrimString(sm[0][1])
				}
			}
			//vb := util.TrimString(v.Body)
			vb := strings.ReplaceAll(v.Body, "：", ":")
			item.Body = vb
			item.IssueExistTpl = models.IssueExistByNumber(v.Number, v.Id)
			//logs.Info("item===> ", item)
			err := item.InsertOrUpdate(1)
			if err != nil {
				logs.Error("insert or update issue fail:", err)
			}
		}
	}
}

//GetOrgInfo get  organization information
func GetOrgInfo(accToken, org string) (OrgInfo, error) {
	oi := OrgInfo{}
	resp, err := http.Get(fmt.Sprintf(GiteOrgInfoURL, org, accToken))
	if err != nil {
		return oi, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return oi, err
	}
	err = json.Unmarshal(body, &oi)
	return oi, err
}

//GetOrgRepos get organization repository
func GetOrgRepos(accToken, org string, page int64) {
	wg.Add(1)
	defer wg.Done()
	resp, err := http.Get(fmt.Sprintf(GiteOrgReposURL, org, accToken, page, perPage))
	if err != nil {
		logs.Error("Get, GiteOrgReposURL: ", GiteOrgReposURL, ", org: ", GiteOrgReposURL, ",err: ", err)
		return
	}
	defer resp.Body.Close()
	var reps []models.HookRepository
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		logs.Error("ReadAll, GiteOrgReposURL: ", GiteOrgReposURL, ", org: ", GiteOrgReposURL, ",err: ", err)
		return
	}
	err = json.Unmarshal(body, &reps)
	if err != nil {
		logs.Error("Unmarshal, GiteOrgReposURL: ", GiteOrgReposURL, ", org: ", GiteOrgReposURL, ",err: ", err)
		return
	}
	for _, v := range reps {
		GrabIssueByRepo(accToken, org, v.Name, "all")
	}
}

//GetIssueList get the repository issue list
func GetIssueList(accToken, owner, repo, state string, page int) (issueList []models.GitIssue, err error) {
	giteUrl := fmt.Sprintf(GiteRepoIssuesURL, owner, repo, accToken, state, page, perPage)
	resp, err := http.Get(giteUrl)
	if err != nil {
		logs.Error("Get, GiteRepoIssuesURL: ", giteUrl, ", repo: ", repo, ", err: ", err)
		return issueList, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		logs.Error("ReadAll, GiteRepoIssuesURL: ", giteUrl, ", repo: ", repo, ", err: ", err)
		return issueList, err
	}
	//logs.Info("-----------issue list: ", string(body))
	err = json.Unmarshal(body, &issueList)
	if err != nil {
		logs.Error("Unmarshal, GiteRepoIssuesURL: ", giteUrl, ", repo: ", repo, ", err: ", err)
	}
	//logs.Info("++++++++++issueList: ", issueList)
	return
}

//GetRepoDescription get repository description
func GetRepoDescription(repo string) (desc string) {
	if repo == "" {
		return ""
	}
	url := fmt.Sprintf(RepoInfoURL, repo)
	resp, err := http.Get(url)
	if err != nil {
		logs.Error("Get, RepoInfoURL: ", url, ",err: ", err)
		return ""
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		logs.Error("ReadAll, RepoInfoURL: ", url, ",err: ", err)
		return ""
	}
	var pkg PackageInfo
	err = json.Unmarshal(body, &pkg)
	if err != nil {
		logs.Error("Unmarshal, RepoInfoURL: ", url, ",err: ", err)
		return ""
	}
	if pkg.Code == "2001" {
		return pkg.Data.Description
	}
	return ""
}
